/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include <limits.h>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;   // RecordFile containing the table
	RecordId   rid;  // record cursor for table scanning

	RC     rc;
	int    key;     
	string value;
	int    count;
	int    diff;

	// open the table file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}

	BTreeIndex bt_index;
	IndexCursor idx_cursor;
	int target;
	int f_equals = -1;		// Value of equal target
	int f_min = -1;			// Min value of range search
	int f_max = INT_MAX;	// Max value of range search
	bool f_GE = false;	// true if >=, false if >
	bool f_LE = false;	// true if <=, false if <
	count = 0;
	
	// Open index
	rc = bt_index.open(table + ".idx", 'r');
	// If no index found or not a "key", go to full table scan
	if (rc < 0 || cond.size() == 0)
		goto full_table_scan;
	
	// check the conditions on the select and set flags
	for (unsigned i = 0; i < cond.size(); i++) {
		target = atoi(cond[i].value);
		
		switch(cond[i].comp) {
			case SelCond::EQ:
				// If 2nd equal condition that isn't the target
				// return 0 results
				if (f_equals != -1 && f_equals != target)
					return 0;
				else 
					f_equals = target;
				break;
			case SelCond::NE:
				// NE doesn't use indexes
				break;
			case SelCond::GT:
				if (target > f_min)
					f_min = target;
				break;
			case SelCond::LT:
				if (target < f_max)
					f_max = target;
				break;
			case SelCond::GE:
				if (target >= f_min) {
					f_min = target;
					f_GE = true;
				}
				break;
			case SelCond::LE:
				if (target <= f_max) {
					f_max = target;
					f_LE = true;
				}
				break;
		}
	}
	
	//printf("Break 1\n");
	// select equality condition
	if (f_equals > 0) {
		// finish search if key is out of range
		if (f_min > f_equals || f_max < f_equals)
			goto done;
		// find target in BTreeIndex
		if ((rc = bt_index.locate(f_equals, idx_cursor)) < 0)
			goto done;
		if ((rc = bt_index.readForward(idx_cursor, key, rid)) < 0)
			goto done;
		
		// read the tuple based on condition
		if ((rc = rf.read(rid, key, value)) < 0) {
			fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
			goto exit_select;
		}
		
		//printf("Break 2\n");
		// check the other conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
			// compute the difference between the tuple value and the condition value
			diff = key - atoi(cond[i].value);

			// skip the tuple if any condition is not met
			switch (cond[i].comp) {
				case SelCond::EQ:
					if (diff != 0) goto done;
					break;
				case SelCond::NE:
					if (diff == 0) goto done;
					break;
				case SelCond::GT:
					if (diff <= 0) goto done;
					break;
				case SelCond::LT:
					if (diff >= 0) goto done;
					break;
				case SelCond::GE:
					if (diff < 0) goto done;
					break;
				case SelCond::LE:
					if (diff > 0) goto done;
					break;
			}
		}

		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;
		
		// print the tuple 
		switch (attr) {
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
		}
	}
	// select based on range 
	else {
		if ((rc = bt_index.locate(f_min, idx_cursor)) < 0)
			goto done;
		
		//printf("Break 3\n");
		while ((rc = bt_index.readForward(idx_cursor, key, rid)) >= 0) {
			if (key <= 0)
				continue;
			// Skip min tuple if not GE
			if (!f_GE && key == f_min) 
				continue;
			// Skip max tuple if not LE
			if (!f_LE && key == f_max)
				continue;
			// Finish if we go out of range
			if (key > f_max)
				break;
		
			// read the tuple based on condition
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
		
			//printf("Break again.\n");
			// check the other conditions on the tuple
			for (unsigned i = 0; i < cond.size(); i++) {
				// compute the difference between the tuple value and the condition value
				diff = key - atoi(cond[i].value);

				// skip the tuple if any condition is not met
				switch (cond[i].comp) {
					case SelCond::EQ:
						if (diff != 0) 
							continue;
						break;
					case SelCond::NE:
						if (diff == 0) 
							continue;
						break;
					case SelCond::GT:
						if (diff <= 0) 
							continue;
						break;
					case SelCond::LT:
						if (diff >= 0) 
							continue;
						break;
					case SelCond::GE:
						if (diff < 0) 
							continue;
						break;
					case SelCond::LE:
						if (diff > 0) 
							continue;
						break;
				}
			}
			
			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;
		
			// print the tuple 
			switch (attr) {
				case 1:  // SELECT key
					fprintf(stdout, "%d\n", key);
					break;
				case 2:  // SELECT value
					fprintf(stdout, "%s\n", value.c_str());
					break;
				case 3:  // SELECT *
					fprintf(stdout, "%d '%s'\n", key, value.c_str());
					break;
			}
		}
	}
	goto done;
  
  full_table_scan:
  
  //fprintf(stderr, "Full scan\n");
  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
		diff = key - atoi(cond[i].value);
		break;
      case 2:
		diff = strcmp(value.c_str(), cond[i].value);
		break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
		if (diff != 0) goto next_tuple;
		break;
      case SelCond::NE:
		if (diff == 0) goto next_tuple;
		break;
      case SelCond::GT:
		if (diff <= 0) goto next_tuple;
		break;
      case SelCond::LT:
		if (diff >= 0) goto next_tuple;
		break;
      case SelCond::GE:
		if (diff < 0) goto next_tuple;
		break;
      case SelCond::LE:
		if (diff > 0) goto next_tuple;
		break;
      }
    }

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    ++rid;
  }

  
  done:
  
	// print matching tuple count if "select count(*)"
	if (attr == 4) {
		fprintf(stdout, "%d\n", count);
	}
	rc = 0;

	
  exit_select:
	// close the table file and return
	rf.close();
	bt_index.close();
	
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	/* Eric's code here */
	RC rc;
	
	// Open input load file
	ifstream f_load;
	f_load.open(loadfile.c_str());
	
	// Check if load file is open
	if (!f_load.is_open()) {
		fprintf(stderr, "Could not open load file\n");
		return RC_FILE_OPEN_FAILED;
	}
  
	// Open record file ('w' mode creates a table if it does not exist)
	RecordFile f_record;
	f_record.open(table + ".tbl", 'w');
	
	// Open index file
	BTreeIndex bt_index;
	if (index) {
		bt_index.open(table + ".idx", 'w');
	}
  
	// Read from load file and append into record file
	string tuple;
	while (getline(f_load, tuple)) {
		int key;
		string value;
		RecordId rid;
		
		parseLoadLine(tuple, key, value);
		f_record.append(key, value, rid);
		
		// Add key to index 
		if (index) {
			bt_index.insert(key, rid);
		}
	}
	
	// Close all files and index
	f_load.close();
	f_record.close();
	if (index) {
		bt_index.close();
	}
	
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
